package com.mrzhou.web.feign;

import com.alibaba.cloud.sentinel.feign.SentinelContractHolder;
import com.alibaba.cloud.sentinel.feign.SentinelInvocationHandler;
import feign.Contract;
import feign.Feign;
import feign.InvocationHandlerFactory;
import feign.Target;
import feign.hystrix.FallbackFactory;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeansException;
import org.springframework.cloud.openfeign.FeignContext;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.util.ReflectionUtils;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.util.Map;

@Slf4j
public class CloudSentienlFeign {

    public static CloudSentienlFeign.Builder builder() {
        return new CloudSentienlFeign.Builder();
    }

    public static final class Builder extends Feign.Builder implements ApplicationContextAware {

        private Contract contract = new Contract.Default();

        private FeignContext feignContext;

        private ApplicationContext applicationContext;

        @Override
        public Feign.Builder invocationHandlerFactory(InvocationHandlerFactory invocationHandlerFactory) {
            throw new UnsupportedOperationException();
        }

        @Override
        public Feign.Builder contract(Contract contract) {
            this.contract = contract;
            return this;
        }

        @Override
        public Feign build() {
            super.invocationHandlerFactory(new InvocationHandlerFactory() {
                @Override
                public InvocationHandler create(Target target, Map<Method, MethodHandler> dispatch) {
                    Object feignClientFactoryBean = Builder.this.applicationContext.getBean("&" + target.type().getName());
                    Class fallback = (Class) this.getFieldValue(feignClientFactoryBean, "fallback");
                    Class fallbackFactory = (Class) this.getFieldValue(feignClientFactoryBean, "fallbackFactory");
                    String name = (String) this.getFieldValue(feignClientFactoryBean, "name");

                    Object fallbackInstance;
                    FallbackFactory fallbackFactoryInstance;

                    if (void.class != fallback) {
                        fallbackInstance = getFromContext(name, "fallback", fallback,
                                target.type());
                        return this.getSentinelInvocationHandler(target, dispatch, new FallbackFactory.Default(fallbackInstance));
                    }
                    if (void.class != fallbackFactory) {
                        fallbackFactoryInstance = (FallbackFactory) getFromContext(name,"fallbackFactory", fallbackFactory, FallbackFactory.class);
                        return this.getSentinelInvocationHandler(target, dispatch, fallbackFactoryInstance);
                    }

                    // 添加默认的fallbackFactory
                    CloudFallbackFactory cloudFallbackFactory = new CloudFallbackFactory(target);
                    return this.getSentinelInvocationHandler(target, dispatch, cloudFallbackFactory);
                }

                private Object getFromContext(String name, String type, Class fallbackType, Class targetType) {
                    Object fallbackInstance = feignContext.getInstance(name, fallbackType);
                    if (fallbackInstance == null) {
                        throw new IllegalStateException(String.format("No %s instance of type %s found for feign client %s", type, fallbackType, name));
                    }
                    if (!targetType.isAssignableFrom(fallbackType)) {
                        throw new IllegalStateException(String.format(
                                "Incompatible %s instance. Fallback/fallbackFactory of type %s is not assignable to %s for feign client %s",
                                type, fallbackType, targetType, name));
                    }
                    return fallbackInstance;
                }

                private Object getFieldValue(Object instance, String fieldName) {
                    Field field = ReflectionUtils.findField(instance.getClass(), fieldName);
                    field.setAccessible(true);
                    try {
                        return field.get(instance);
                    } catch (IllegalAccessException e) {
                        // ignore
                    }
                    return null;
                }

                private SentinelInvocationHandler getSentinelInvocationHandler(Target target, Map<Method, MethodHandler> dispatch, FallbackFactory factory) {
                    try {
                        Constructor<SentinelInvocationHandler> constructor = SentinelInvocationHandler.class.getDeclaredConstructor(Target.class, Map.class, FallbackFactory.class);
                        constructor.setAccessible(true);
                        return constructor.newInstance(target, dispatch, factory);
                    } catch (Exception e) {
                        log.error("获取SentinelInvocationHandler的构造方法失败");
                        throw new RuntimeException(e);
                    }
                }
            });
            super.contract(new SentinelContractHolder(contract));
            return super.build();
        }

        public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
            this.applicationContext = applicationContext;
            feignContext = this.applicationContext.getBean(FeignContext.class);
        }
    }


}
